Skip to content

使用 Google Apps Script 同步 Markdown 筆記至 NotebookLM

TLDR

  • NotebookLM 不支援直接同步 Markdown 檔案,且重複上傳會產生重複來源,無法批次更新。
  • 透過 Google Apps Script (GAS) 可將本機 Markdown 檔案自動轉換並更新為 Google Docs,進而觸發 NotebookLM 的同步機制。
  • 核心邏輯:利用 GAS 遞迴掃描來源資料夾,將 Markdown 內容寫入 Google Docs,並透過 getLastUpdated() 判斷是否需要更新,確保 NotebookLM 的引用連結不中斷。
  • 建議將同步邏輯封裝為 Library,並透過「時間驅動」觸發器達成自動化。
  • 處理長文字時,必須使用分批寫入 (Chunking) 策略,避免超過 GAS 的執行限制。

NotebookLM 雖然具備強大的 RAG 能力,但對於習慣持續迭代 Markdown 筆記的使用者而言,其檔案管理機制存在明顯痛點:

  1. 重複上傳問題:上傳相同檔名的檔案會被視為全新來源,導致舊來源與新來源並存,無法自動覆蓋。
  2. 格式支援限制:不支援直接同步 .md 檔案。
  3. 目錄結構繁瑣:無法批次選取子資料夾內的檔案進行同步。

為了解決這些問題,我們可以透過 Google Apps Script (GAS) 建立一個「中轉服務」,將 Markdown 轉換為 Google Docs,並利用 Google Drive 的同步機制與 NotebookLM 串接。

Google Apps Script 實作流程

1. 建立專案與設定參數

script.google.com 建立新專案後,將核心邏輯貼入 程式碼.gs。你需要設定來源與目標資料夾 ID,並定義黑白名單以過濾檔案。

javascript
function syncCloudyWingLog() {
  const sourceFolderId = 'Source Folder Id';
  const targetFolderId = 'Target Folder Id';

  // 設定檔案白名單 (只同步 .md)
  const whitelistFiles = [/\.md$/i];
  // 設定檔案黑名單
  const blacklistFiles = [/^index\.md$/i, /^about\.md$/i, /^tags\.md$/i];

  SyncUtils.startSyncEngine(
    sourceFolderId,
    targetFolderId,
    whitelistFiles,
    blacklistFiles,
    [],
    []
  );
}

2. 核心同步邏輯

為了確保 NotebookLM 的對話紀錄不中斷,我們必須維持 Google Doc 的檔案 ID 不變,僅在內容更新時進行覆寫。

javascript
/**
 * 將單一檔案同步至 Google Doc
 */
function syncFileToGoogleDoc(sourceFile, targetFolder, targetDocName) {
  const existingFiles = targetFolder.getFilesByName(targetDocName);
  let targetDocFile = null;

  while (existingFiles.hasNext()) {
    const f = existingFiles.next();
    if (f.getMimeType() === MimeType.GOOGLE_DOCS) {
      targetDocFile = f;
      break;
    }
  }

  if (targetDocFile) {
    // 僅在來源檔案更新時才寫入,避免不必要的 API 消耗
    if (sourceFile.getLastUpdated() > targetDocFile.getLastUpdated()) {
      updateDocContent(targetDocFile.getId(), sourceFile);
    }
  } else {
    createDocContent(targetFolder, targetDocName, sourceFile);
  }
}

3. 分批寫入策略 (Chunking Strategy)

什麼情況下會遇到這個問題:當 Markdown 筆記內容過長,直接寫入會導致 GAS 執行逾時或記憶體溢位。

javascript
function writeContentInChunks(docBody, fullText) {
  const CHUNK_SIZE = 20000;
  docBody.setText(""); // 清空舊內容

  for (let i = 0; i < fullText.length; i += CHUNK_SIZE) {
    const chunk = fullText.substring(i, i + CHUNK_SIZE);
    const paragraphs = docBody.getParagraphs();
    const lastParagraph = paragraphs[paragraphs.length - 1];

    if (lastParagraph) {
      lastParagraph.appendText(chunk);
    } else {
      docBody.appendParagraph(chunk);
    }
    Utilities.sleep(150); // 避免 API Rate Limiting
  }
}

4. 部署與自動化

  • Library 封裝:將核心邏輯部署為 Library,透過「指令碼 ID」在其他專案中引用,實現程式碼重用與解耦。
  • 自動化觸發:點擊左側「觸發條件」,設定「時間驅動」觸發器(例如每小時執行一次 syncCloudyWingLog),即可達成背景自動同步。

技術選型總結

在選擇知識庫方案時,曾評估過以下工具,但因特定需求未達標而放棄:

  • AnythingLLM:雖然功能強大,但需要進行向量索引 (Vector Indexing) 匯入,且參數(Chunk Size/Overlap)調校成本高,對於筆記搜尋的精準度要求難以滿足。
  • 本地 AI 模型 (LM Studio/Ollama/KoboldCpp):雖然隱私性高,但受限於 VRAM 資源與硬體效能,且缺乏像 NotebookLM 那樣針對長文上下文的優化處理。

結論:對於無隱私疑慮的筆記,NotebookLM 仍是目前 RAG 效果與維護成本平衡點最高的選擇。透過 GAS 解決檔案同步問題後,可大幅降低手動維護的繁瑣度。


異動歷程

    • 初版文件建立。